package club.kingyin.easycache.core.config;

import club.kingyin.easycache.cache.CacheAdapter;
import club.kingyin.easycache.cache.ValueSerializer;
import club.kingyin.easycache.cache.inner.InnerCache;
import club.kingyin.easycache.cache.redis.RedisCache;
import club.kingyin.easycache.cache.union.Level2Cache;
import club.kingyin.easycache.component.CachePostProcess;
import club.kingyin.easycache.component.config.CacheConfiguration;
import club.kingyin.easycache.component.config.Config;
import club.kingyin.easycache.component.handler.Serializer;
import club.kingyin.easycache.core.PointEasyCache;
import club.kingyin.easycache.exception.InitException;
import club.kingyin.easycache.exception.SerializeException;
import club.kingyin.easycache.key.InvokePostProcess;
import club.kingyin.easycache.method.CacheMethod;
import club.kingyin.easycache.method.core.PointEasyCacheEnhancer;
import club.kingyin.easycache.utils.AnnotationUtils;
import com.github.houbb.cache.core.support.load.CacheLoads;
import com.github.houbb.cache.core.support.persist.CachePersists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import redis.clients.jedis.JedisPoolConfig;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.util.Set;

@Slf4j
@Configuration
@EnableConfigurationProperties({EasyCacheProperties.class,Inner.class,Redis.class,Engine.class})
@Import({PointEasyCache.class})
@ConditionalOnClass({PointEasyCache.class})
public class EasyCacheConfiguration {

    @Autowired
    private EasyCacheProperties easyCacheProperties;

    @Autowired
    private Engine engine;

    @Autowired
    private Inner inner;

    @Autowired
    private Redis redis;

    private InnerCache innerCache;

    private RedisCache redisCache;

    @Bean
    @Conditional({EngineSwitch.class})
    public PointEasyCacheEnhancer pointEasyCacheEnhancer() throws ClassNotFoundException {
        // 全局上下文配置
        if (StringUtils.isNotBlank(easyCacheProperties.getType())) {
            CacheConfiguration.set(Config.CONTEXT, Class.forName(easyCacheProperties.getType()));
        }
        // 全局上下文静态方法配置
        if (StringUtils.isNotBlank(easyCacheProperties.getFunc())) {
            CacheConfiguration.set(Config.CONTEXT_GET, easyCacheProperties.getFunc());
        }
        // Spring 核心切点构造
        PointEasyCacheEnhancer easyCacheEnhancer = new PointEasyCacheEnhancer();
        // 解析引擎
        easyCacheEnhancer.setDatabase(structureEngine());
        // 设置序列化器
        easyCacheEnhancer.setSerializer(getByConfig());
        // 注册处理器
        AnnotationUtils.registerAnnotationHandler(easyCacheEnhancer);

        return easyCacheEnhancer;
    }
    @Bean
    @ConditionalOnProperty(value = "easycache.engine.module", havingValue = "redis")
    public RedisCache redisCache() {
        return null == redisCache ? redisCache =  structureRedisCacheEngine(redis) : redisCache;
    }

    @Bean
    @ConditionalOnProperty(value = "easycache.engine.module", havingValue = "inner")
    public InnerCache innerCache() {
        return null == innerCache ? innerCache = structureInnerCacheEngine(inner, engine) : innerCache;
    }

    private CacheAdapter structureEngine() {
        switch (engine.getModule()) {
            case "level2":
                return structureLevel2CacheEngine(inner, redis, engine);
            case "inner":
                return innerCache();
            case "redis":
                return redisCache();
            default: throw new InitException("无法解析缓存引擎 【"+engine.getModule()+"】 可选【level2】|【inner】|【redis】");
        }
    }

    // 创建引擎 inner
    private InnerCache structureInnerCacheEngine(Inner inner, Engine engine) {
        log.info("Inner 配置 {}", inner);
        InnerCache.Builder builder = new InnerCache.Builder();
        if (EasyCacheProperties.EMPTY_STRING.equals(inner.getPath())) {
            inner.setPath(defaultDbPath());
        }
        if (new File(inner.getPath()).exists()) {
            // 需要加载数据
            builder.load(CacheLoads.dbJson(inner.getPath()));
        }
        builder.size(inner.getSize());

        InnerCache adapter = builder
                .persist(CachePersists.dbJson(inner.getPath())).build();
        adapter.setSerializer(getByConfig());
        return adapter;
    }

    private ValueSerializer getByConfig() {
        switch (engine.getSerializer()) {
            case "gson":
            case "GSON":
                return Serializer.GSON;
            case "json":
            case "JSON":
                log.warn("不推荐使用【JSON】序列化");
                return Serializer.JSON;
            default: throw new SerializeException("不支持序列化 '"+engine.getSerializer()+"' 可选【gson】|【json】");
        }
    }

    // 创建引擎 redis
    private RedisCache structureRedisCacheEngine(Redis redis) {
        log.info("Redis 配置 {}", redis);
        RedisCache.Builder builder = new RedisCache.Builder();
        builder.serializer(getByConfig());
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMinIdle(redis.getMinIdle());
        config.setMaxIdle(redis.getMaxIdle());
        config.setMaxWait(Duration.ofMillis(redis.getMaxWait()));
        config.setTimeBetweenEvictionRuns(Duration.ofMillis(redis.getTimeBetweenEvictionRuns()));
        config.setMinEvictableIdleTime(Duration.ofMillis(redis.getMinEvictableIdleTime()));
        builder.config(config);
        builder.port(redis.getPort());
        if (!redis.getPassword().equals(EasyCacheProperties.EMPTY_STRING)) {
            builder.password(redis.getPassword());
        }
        builder.host(redis.getHost());
        RedisCache cache = builder.build();
        cache.ops().set("easycache", "hello word", 60);
        return cache;
    }

    private CacheAdapter structureLevel2CacheEngine(Inner inner, Redis redis, Engine engine) {
        return new Level2Cache(structureInnerCacheEngine(inner,engine),structureRedisCacheEngine(redis));
    }

    // 注册 系统处理器+自定义全局处理器
    private void registerHandler(EasyCacheProperties easyCacheProperties,PointEasyCacheEnhancer enhancer) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        Set<InvokePostProcess<CacheMethod>> invokePostProcesses = AnnotationUtils.invokePostProcess();
        Set<CachePostProcess> cachePostProcess = AnnotationUtils.cachePostProcess();

        enhancer.setCachePostProcesses(cachePostProcess.toArray(new CachePostProcess[0]));
        enhancer.setInvokePostProcesses(invokePostProcesses.toArray(new InvokePostProcess[0]));
    }

    public static String defaultDbPath() {
        return System.getProperty("user.dir") + "/src/main/resources/easycache.rdb";
    }


}
